import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { z } from 'zod'
import { zodToJsonSchema } from 'zod-to-json-schema'
import {
  CallToolRequestSchema,
  CompleteRequestSchema,
  CreateMessageRequest,
  CreateMessageResultSchema,
  GetPromptRequestSchema,
  ListPromptsRequestSchema,
  ListResourcesRequestSchema,
  ListResourceTemplatesRequestSchema,
  ListToolsRequestSchema,
  LoggingLevel,
  ReadResourceRequestSchema,
  Resource,
  SetLevelRequestSchema,
  SubscribeRequestSchema,
  Tool,
  ToolSchema,
  UnsubscribeRequestSchema,
} from '@modelcontextprotocol/sdk/types.js'
import { readFileSync } from 'fs'
import { fileURLToPath } from 'url'
import { dirname, join } from 'path'
import { messages, serverInfo } from './const.js'
import {
  generateLineChart,
  generateBarChart,
  generatePieChart,
} from './options/index.js'

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
const instructions = readFileSync(join(__dirname, 'instructions.md'), 'utf-8')

const ToolInputSchema = ToolSchema.shape.inputSchema
type ToolInput = z.infer<typeof ToolInputSchema>

const ToolOutputSchema = ToolSchema.shape.outputSchema
type ToolOutput = z.infer<typeof ToolOutputSchema>

const PrintEnvSchema = z.object({})

const GenerateChartSchema = z.object({
  chartType: z
    .enum(['line', 'bar', 'pie'])
    .describe('Type of chart to generate'),
  title: z.string().optional().describe('Chart title'),
  xAxisData: z.array(z.any()).describe('X-axis data array'),
  yAxisData: z.array(z.any()).describe('Y-axis data array'),
  seriesName: z.string().optional().describe('Series name for legend'),
  xAxisName: z.string().optional().describe('X-axis name'),
  yAxisName: z.string().optional().describe('Y-axis name'),
  theme: z.enum(['light', 'dark']).optional().describe('Chart theme'),
})

enum ToolName {
  PRINT_ENV = 'printEnv',
  GENERATE_CHART = 'generateChart',
}

export const createServer = () => {
  const server = new Server(serverInfo, {
    capabilities: {
      resources: { subscribe: true },
      tools: {},
      logging: {},
    },
    instructions,
  })

  let subscriptions: Set<string> = new Set()
  let subsUpdateInterval: NodeJS.Timeout | undefined
  let stdErrUpdateInterval: NodeJS.Timeout | undefined
  let logLevel: LoggingLevel = 'debug'
  let logsUpdateInterval: NodeJS.Timeout | undefined

  const isMessageIgnored = (level: LoggingLevel): boolean => {
    const currentLevel = messages.findIndex((msg) => logLevel === msg.level)
    const messageLevel = messages.findIndex((msg) => level === msg.level)
    return messageLevel < currentLevel
  }

  // Start notification intervals when a client connects
  const startNotification = () => {
    if (!subsUpdateInterval) {
      subsUpdateInterval = setInterval(() => {
        for (const uri of subscriptions) {
          server.notification({
            method: 'notifications/resources/updated',
            params: { uri },
          })
        }
      }, 10000)
    }

    if (!logsUpdateInterval) {
      logsUpdateInterval = setInterval(() => {
        let message = {
          method: 'notifications/message',
          params: messages[1],
        }
        if (!isMessageIgnored(message.params.level as LoggingLevel))
          server.notification(message)
      }, 20000)
    }
  }

  const requestSampling = async (
    context: string,
    uri: string,
    maxTokens: number = 100
  ) => {
    const request: CreateMessageRequest = {
      method: 'sampling/createMessage',
      params: {
        messages: [
          {
            role: 'user',
            content: {
              type: 'text',
              text: `Resource ${uri} context: ${context}`,
            },
          },
        ],
        systemPrompt: 'You are a helpful test server.',
        maxTokens,
        temperature: 0.7,
        includeContext: 'thisServer',
      },
    }

    return await server.request(request, CreateMessageResultSchema)
  }

  server.setRequestHandler(SubscribeRequestSchema, async (request) => {
    const { uri } = request.params
    subscriptions.add(uri)
    await requestSampling('A new subscription was started', uri)
    return {}
  })

  server.setRequestHandler(UnsubscribeRequestSchema, async (request) => {
    subscriptions.delete(request.params.uri)
    return {}
  })

  server.setRequestHandler(ListResourcesRequestSchema, async () => {
    return {
      resources: [
        {
          uri: 'file:///config.json',
          name: 'Configuration',
          description: 'Server configuration file',
          mimeType: 'application/json',
        },
      ],
    }
  })

  server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
    const { uri } = request.params

    if (uri === 'file:///config.json') {
      const configContent = readFileSync(
        join(__dirname, 'config.json'),
        'utf-8'
      )
      return {
        contents: [
          {
            uri,
            mimeType: 'application/json',
            text: configContent,
          },
        ],
      }
    }

    throw new Error(`Resource not found: ${uri}`)
  })

  server.setRequestHandler(ListToolsRequestSchema, async () => {
    const tools: Tool[] = [
      {
        name: ToolName.PRINT_ENV,
        description:
          'Prints all environment variables, helpful for debugging MCP server configuration',
        inputSchema: zodToJsonSchema(PrintEnvSchema) as ToolInput,
      },
      {
        name: ToolName.GENERATE_CHART,
        description:
          'Generate ECharts configuration for various chart types. Supports line charts, bar charts, pie charts with customizable data, titles, colors, and styling options.',
        inputSchema: zodToJsonSchema(GenerateChartSchema) as ToolInput,
      },
    ]

    return { tools }
  })

  server.setRequestHandler(CallToolRequestSchema, async (request) => {
    const { name, arguments: args } = request.params

    if (name === ToolName.PRINT_ENV) {
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(process.env, null, 2),
          },
        ],
      }
    }

    if (name === ToolName.GENERATE_CHART) {
      try {
        const validatedArgs = GenerateChartSchema.parse(args)

        let chartConfig
        switch (validatedArgs.chartType) {
          case 'line':
            chartConfig = generateLineChart(validatedArgs)
            break
          case 'bar':
            chartConfig = generateBarChart(validatedArgs)
            break
          case 'pie':
            chartConfig = generatePieChart(validatedArgs)
            break
          default:
            throw new Error(
              `[Tool] Unsupported chart type: ${validatedArgs.chartType}`
            )
        }

        return {
          content: [
            {
              type: 'text',
              text: `${JSON.stringify(chartConfig, null, 2)}`,
            },
          ],
          // content: [{
          //   type: 'chart_config',
          //   config: chartConfig
          // }]
        }
      } catch (error: any) {
        return {
          content: [
            {
              type: 'text',
              text: `Error generating chart: ${error.message}`,
            },
          ],
          isError: true,
        }
      }
    }

    throw new Error(`[Tool] Unknown tool: ${name}`)
  })

  server.setRequestHandler(SetLevelRequestSchema, async (request) => {
    const { level } = request.params
    logLevel = level

    await server.notification({
      method: 'notifications/message',
      params: {
        level: 'debug',
        logger: 'test-server',
        data: `Logging level set to: ${logLevel}`,
      },
    })

    return {}
  })

  const cleanup = async () => {
    if (subsUpdateInterval) clearInterval(subsUpdateInterval)
    if (logsUpdateInterval) clearInterval(logsUpdateInterval)
    if (stdErrUpdateInterval) clearInterval(stdErrUpdateInterval)
  }

  return { server, cleanup, startNotification }
}
